package com.biz.primus.ms.sms.service.internal;

import com.biz.primus.model.misc.exception.ProviderNotAvailableException;
import com.biz.primus.model.misc.exception.SystemException;
import com.biz.primus.model.misc.vo.ProviderInfoVo;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.sms.dao.po.SelectedProvider;
import com.biz.primus.ms.sms.dao.repository.SelectedProviderRepository;
import com.biz.primus.ms.sms.service.model.ServiceProvider;
import com.biz.primus.ms.sms.service.model.StatefulServiceProvider;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static com.biz.primus.model.misc.constants.SMSConstants.CURRENT_PROVIDER_CACHE_KEY;

/**
 * @author yanweijin
 * @date 2017/5/31
 */
@Service
@Slf4j
public class ProviderSelector extends AbstractBaseService {

    private final SelectedProviderRepository selectedProviderRepository;

    private final List<ServiceProvider> providers;

    private LoadingCache<String, Optional<SelectedProvider>> currentProviderCache = CacheBuilder.newBuilder()
            .expireAfterWrite(5, TimeUnit.SECONDS).build(new CacheLoader<String, Optional<SelectedProvider>>() {
                @Override
                public Optional<SelectedProvider> load(@NotNull String key) throws Exception {
                    return selectedProviderRepository.findFirstByOrderByIdDesc();
                }
            });

    @Autowired
    public ProviderSelector(SelectedProviderRepository selectedProviderRepository, List<ServiceProvider> providers) {
        this.selectedProviderRepository = selectedProviderRepository;
        this.providers = providers;
    }


    public ServiceProvider findCurrentProvider() throws NullPointerException {
        Optional<SelectedProvider> selectedProviderOptional = currentProviderCache.getUnchecked(CURRENT_PROVIDER_CACHE_KEY);
        if (selectedProviderOptional.isPresent()) {
            final String providerId = selectedProviderOptional.get().getProviderId();
            Optional<ServiceProvider> serviceProviderOptional = Iterables.tryFind(providers, ProviderFinder.byProviderId(providerId));
            if (serviceProviderOptional.isPresent()) {
                return serviceProviderOptional.get();
            }
            log.warn("没有找到对应配置的供应商,进入默认匹配阶段,selectedProviderId={}", selectedProviderOptional.get());
        } else {
            log.warn("没有配置选择的供应商,进入默认匹配阶段");
        }

        Optional<ServiceProvider> defaultProviderOptional = Iterables.tryFind(providers, ProviderFinder.byDefault);
        if (defaultProviderOptional.isPresent()) {
//			logger.info("获取默认供应商:{}",defaultProviderOptional.get().getProviderName());
            return defaultProviderOptional.get();
        }

        throw new SystemException("找不到供应商");
    }

    public ServiceProvider findProvider(String providerId) throws NullPointerException {
        return Iterables.find(providers, ProviderFinder.byProviderId(providerId));
    }

    @Transactional
    public void setCurrentProvider(String providerId) throws NullPointerException {
        //null check
        Iterables.find(providers, ProviderFinder.byProviderId(providerId));
        selectedProviderRepository.save(new SelectedProvider(providerId));
    }

    public Set<StatefulServiceProvider> findAllStatefulProviders() {
        //FIXME
        throw new UnsupportedOperationException();
    }

    public List<ProviderInfoVo> findAllProviders() {

        Optional<SelectedProvider> selectedProviderOptional = currentProviderCache.getUnchecked(CURRENT_PROVIDER_CACHE_KEY);
        final String currentProductId = selectedProviderOptional.isPresent() ? selectedProviderOptional.get().getProviderId() : null;

        return Lists.transform(providers, sp -> {
            ProviderInfoVo vo = new ProviderInfoVo();
            vo.setProviderId(sp.getProviderId());
            vo.setProviderName(sp.getProviderName());
            vo.setDefaultProvider(sp.isDefaultProvider());
            vo.setCurrentSelected(Objects.equal(sp.getProviderId(), currentProductId));
            try {
                vo.setBalance(sp.getBalance());
            } catch (ProviderNotAvailableException e) {
                log.warn("get provider [{}] balance fail", sp.getProviderName(), e);
            }
            return vo;
        });
    }

    public Optional<ServiceProvider> findProviderExcludeSome(Set<String> excludedProviderIds) {
        return Iterables.tryFind(providers, ProviderFinder.byExcludeProviderIds(excludedProviderIds));
    }

    static class ProviderFinder implements Predicate<ServiceProvider> {

        private final String providerId;

        private final Set<String> excludeProviderIds = Sets.newHashSet();

        static ProviderFinder byProviderId(String providerId) {
            Assert.hasText(providerId, "providerId不能为空");
            return new ProviderFinder(providerId, Collections.<String>emptySet());
        }

        final static ProviderFinder byDefault = new ProviderFinder(null, Collections.<String>emptySet());

        static ProviderFinder byExcludeProviderIds(Set<String> excludedProviderIds) {
            return new ProviderFinder(null, excludedProviderIds);
        }

        private ProviderFinder(String providerId, Set<String> excludeProviderIds) {
            this.providerId = providerId;
            this.excludeProviderIds.addAll(excludeProviderIds);

        }

        public boolean apply(ServiceProvider input) {
            if (StringUtils.isNotBlank(providerId)) {
                return Objects.equal(input.getProviderId(), providerId);
            }
            if (!CollectionUtils.isEmpty(excludeProviderIds)) {
                return !excludeProviderIds.contains(input.getProviderId());
            }
            return input.isDefaultProvider();
        }

    }

}
